package com.wolfpire.system.common.base.dao.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.persistence.Id;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import com.wolfpire.system.common.Page;
import com.wolfpire.system.common.base.dao.IBaseHibernateDao;
import com.wolfpire.system.common.util.BeanUtils;
import com.wolfpire.system.common.util.MatchType;
import com.wolfpire.system.common.util.ReflectionUtils;

/**
 * 封装Hibernate原生API的CRUD泛型基类实现类
 * @author lihd
 *
 * @param <T>实体类
 * @param <PK>主键
 */
public class BaseHibernateDao<T, PK extends Serializable> implements IBaseHibernateDao<T, PK> {
	
	protected Logger logger = Logger.getLogger(this.getClass());
	
	protected SessionFactory sessionFactory;
	
	protected Class<T> entityClass;
	
	@SuppressWarnings("unchecked")
	public BaseHibernateDao() {
		super();
		this.entityClass = (Class<T>) ReflectionUtils.getSuperClassGenricType(this.getClass());
	}

	public BaseHibernateDao(SessionFactory sessionFactory, Class<T> entityClass) {
		super();
		this.sessionFactory = sessionFactory;
		this.entityClass = entityClass;
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}
	
	/**
	 * 采用@Autowired按类型注入SessionFactory,当有多个SesionFactory的时候Override本函数.
	 * @param sessionFactory
	 */
	@Autowired
	public void setSessionFactory(final SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
	
	public Session getSession() {
		// 事务必须是开启的(Required)，否则获取不到
		return this.sessionFactory.getCurrentSession();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public PK save(T entity) {
		Assert.notNull(entity, "entity不能为空");
		return (PK) getSession().save(entity);
	}

	@Override
	public void update(T entity) {
		Assert.notNull(entity, "entity不能为空");
		getSession().update(entity);
	}

	@Override
	public void merge(T entity) {
		Assert.notNull(entity, "entity不能为空");
		getSession().merge(entity);
	}

	@Override
	public void saveOrUpdate(T entity) {
		// TODO : 待优化
		Assert.notNull(entity, "entity不能为空");
		try {
			PK id = getPKValue(entity);
			if (null != id) {
				Object origin = getSession().get(this.entityClass, id);
				// TODO : 这里要合并对象属性
				// 将更新状态的对象跟数据库源对象进行比较，合并null值。但是如果是空字符串则进行替换
				BeanUtils.mergeObject(entity, origin);
				getSession().merge(entity);
			} else {
				getSession().save(entity);
			}
		} catch (Exception e) {
			// TODO: handle exception
			logger.info(e.getMessage());
			e.printStackTrace();
			getSession().saveOrUpdate(entity);
		}
	}
	
	/**
	 * 获取对象Id
	 * @param entity
	 * @return
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	@SuppressWarnings("unchecked")
	private PK getPKValue(T entity) throws IllegalArgumentException, IllegalAccessException {
		// TODO Auto-generated method stub
		/**
		 * 基于注解@ID
		 */
		PK id = null;
		Field[] fields = this.entityClass.getDeclaredFields();
        for(Field f : fields) {
            if(f.isAnnotationPresent(Id.class)) {
            	f.setAccessible(true);
                if (null != f.get(entity)) {
                	id = (PK) f.get(entity);
                }
            }
        }
        /**
         * 根据ClassMetadata接口来获取主键名，等信息
         * ClassMetadata meta = getSessionFactory().getClassMetadata(this.entityClass);
		 * Assert.notNull(meta, "Class " + entityClass.getSimpleName() + " not define in HibernateSessionFactory.");
		 * return meta.getIdentifierPropertyName(); 
         */
		return id;
	}

	@Override
	public void delete(PK id) {
		Assert.notNull(id, "id 不能为空");
		getSession().delete(this.get(id));
	}

	@Override
	public void delete(T entity) {
		Assert.notNull(entity, "entity不能为空");
		getSession().delete(entity);
	}

	@Override
	public void delete(PK... ids) {
		Assert.notNull(ids, "ids 不能为空");
		StringBuilder builder = new StringBuilder();
		builder.append(" delete ").append(this.entityClass.getSimpleName())
				.append(" where ").append(this.getPKName()).append(" in (");
		for (int i = 0; i < ids.length; i++) {
			builder.append("?,");
		}
		builder.deleteCharAt(builder.length() - 1);
		builder.append(") ");
//		Object[] objs = Arrays.copyOf(ids, ids.length);
		this.executeHql(builder.toString(), (Object[])ids);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T get(PK id) {
		Assert.notNull(id, "id 不能为空");
		return (T) getSession().get(this.entityClass, id);
	}

	@Override
	public Object findUniqueBySql(String querySql, Object... values) {
		Assert.hasText(querySql, "querySql不能为空");
		Query query = this.createSqlQuery(querySql, values);
		return query.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Object> findBySql(String querySql, Object... values) {
		Assert.hasText(querySql, "querySql不能为空");
		Query query = this.createSqlQuery(querySql, values);
		return query.list();
	}

	@Override
	public Integer findSqlInt(String querySql, Object... values) {
		Assert.hasText(querySql, "querySql不能为空");
		Object rs = this.findUniqueBySql(querySql, values);
		if (null != rs) {
			return new BigInteger(rs.toString()).intValue();
		}
		return new Integer(0);
	}

	@Override
	public Long findSqlLong(String querySql, Object... values) {
		Assert.hasText(querySql, "querySql不能为空");
		Object rs = this.findUniqueBySql(querySql, values);
		if (null != rs) {
			return (Long) rs;
		}
		return new Long(0);
	}

	@Override
	public void executeSql(String executeSql, Object... values) {
		Assert.hasText(executeSql, "executeSql不能为空");
		Query query = this.createSqlQuery(executeSql, values);
		query.executeUpdate();
	}

	@Override
	public boolean exists(PK id) {
		return null != this.get(id);
	}

	@Override
	public boolean isPropertyUnique(String propertyName, Object newValue,
			Object orgValue) {
		if (newValue == null || newValue.equals(orgValue)) {
			return true;
		}
		Object object = this.findUniqueBy(propertyName, newValue);
		return (object == null);
	}

	@Override
	public boolean isPropertyUnique(String propertyName, Object propertyValue,
			PK id) {
		@SuppressWarnings("rawtypes")
		List list = null;
		Criteria c = null;
		Criterion c1 = Restrictions.eq(propertyName, propertyValue);
		if(id != null){
			Criterion c2 = Restrictions.ne(getPKName(), id);
			c = createCriteria(c1, c2);
			list = c.list();
			return null == list || list.size() < 2;
		}else{
			c = createCriteria(c1);
			list = c.list();
			return null == list || list.size() == 0;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public T load(PK id) {
		Assert.notNull(id, "id 不能为空");
		return (T) getSession().load(this.entityClass, id);
	}

	@Override
	public void init(Object proxy) {
		Assert.notNull(proxy, "proxy不能为空");
		Hibernate.initialize(proxy);
	}

	@Override
	public Object findUniqueByHql(String queryHql, Object... values) {
		Assert.hasText(queryHql, "queryHql不能为空");
		Query query = this.createQuery(queryHql, values);
		return query.uniqueResult();
	}

	@Override
	public T findUniqueBy(String propertyName, Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return this.findUniqueByCriteria(criterion);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T findUniqueByCriteria(Criterion... criterions) {
		Criteria criteria = this.createCriteria(criterions);
		return (T) criteria.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByHql(String queryHql, Object... values) {
		Assert.hasText(queryHql, "queryHql不能为空");
		Query query = this.createQuery(queryHql, values);
		return query.list();
	}

	@Override
	public List<T> findBy(String propertyName, Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return this.findByCriteria(criterion);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByCriteria(Criterion... criterions) {
		Criteria criteria = this.createCriteria(criterions);
		return criteria.list();
	}

	@Override
	public Integer findInt(String queryHql, Object... values) {
		Assert.hasText(queryHql, "queryHql不能为空");
		Object rs = this.findUniqueByHql(queryHql, values);
		if (null != rs) {
			return (Integer) rs;
		}
		return new Integer(0);
	}

	@Override
	public Long findLong(String queryHql, Object... values) {
		Assert.hasText(queryHql, "queryHql不能为空");
		Object rs = this.findUniqueByHql(queryHql, values);
		if (null != rs) {
			return (Long) rs;
		}
		return new Long(0);
	}

	@Override
	public void executeHql(String updateHql, Object... values) {
		Assert.hasText(updateHql, "queryHql不能为空");
		Query query = this.createQuery(updateHql, values);
		query.executeUpdate();
	}

	@Override
	public Query createQuery(String queryHql, Object... values) {
		Assert.hasText(queryHql, "queryHql不能为空");
		Query query = getSession().createQuery(queryHql);
		if (ArrayUtils.isNotEmpty(values)) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
		return query;
	}

	@Override
	public Query createSqlQuery(String querySql, Object... values) {
		Assert.hasText(querySql, "querySql不能为空");
		SQLQuery query = getSession().createSQLQuery(querySql);
		if (ArrayUtils.isNotEmpty(values)) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
		return query;
	}

	@Override
	public Criteria createCriteria(Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(this.entityClass);
		if (ArrayUtils.isNotEmpty(criterions)) {
			for (Criterion c : criterions) {
				criteria.add(c);
			}
		}
		return criteria;
	}
	
	private String getPKName() {
		/**
		 * 基于注解@ID 获取主键名称
		 */
		String pkName = "";
		Field[] fields = this.entityClass.getDeclaredFields();
        for(Field f : fields) {
            if(f.isAnnotationPresent(Id.class)) {
                pkName = f.getName();
            }
        }
        Assert.notNull(pkName);
		
        /**
         * 根据ClassMetadata接口来获取主键名，等信息
         * ClassMetadata meta = getSessionFactory().getClassMetadata(this.entityClass);
		 * Assert.notNull(meta, "Class " + entityClass.getSimpleName() + " not define in HibernateSessionFactory.");
		 * return meta.getIdentifierPropertyName(); 
         */
		return pkName;
	}

	@Override
	public Integer findIntByCriteria(Criterion... criterions) {
		List<T> rs = this.findByCriteria(criterions);
		return null != rs ? rs.size() : 0;
	}
	
	/**
	 * 设置分页参数到Criteria对象,辅助函数.
	 */
	protected Criteria setPageParameter(final Criteria c, final Page<T> page) {
		if(page.getPageSize() != 0){
			int start = page.getCurrentPage() * page.getPageSize();
			c.setFirstResult(start);
			c.setMaxResults(page.getPageSize());
		}
		return c;
	}
	
	@SuppressWarnings("unchecked")
	public List<Map<String, Object>> setPageParameter(final Page<Map<String, Object>> page, final String querySql, final Object... values) {
		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		Query query = this.createSqlQuery(querySql, values);
		if (page.getPageSize() != 0) {
			int start = page.getCurrentPage() * page.getPageSize();
			query.setFirstResult(start);
			query.setMaxResults(page.getPageSize());
			list = query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list();
		}
		return list;
	}

	@Override
	public boolean isPropertyUnique(String[] propertyNames, Object[] propertyValues, MatchType[] matchTypes, PK id) {
		@SuppressWarnings("rawtypes")
		List list = null;
		Criteria c = this.createCriteria();
		for (int i = 0; i < matchTypes.length; i++) {
			MatchType matchType = matchTypes[i];
			Criterion criterion = null;
			if (MatchType.EQ.equals(matchType)) {
				criterion = Restrictions.eq(propertyNames[i], propertyValues[i]);
				c.add(criterion);
			} 
			if (MatchType.NE.equals(matchType)) {
				criterion = Restrictions.ne(propertyNames[i], propertyValues[i]);
			}
			if (MatchType.LE.equals(matchType)) {
				criterion = Restrictions.le(propertyNames[i], propertyValues[i]);
			}
			if (MatchType.LT.equals(matchType)) {
				criterion = Restrictions.lt(propertyNames[i], propertyValues[i]);
			}
			if (MatchType.GE.equals(matchType)) {
				criterion = Restrictions.ge(propertyNames[i], propertyValues[i]);
			}
			if (MatchType.GT.equals(matchType)) {
				criterion = Restrictions.gt(propertyNames[i], propertyValues[i]);
			}
			if (MatchType.LIKE.equals(matchType)) {
				criterion = Restrictions.like(propertyNames[i], (String) propertyValues[i], MatchMode.ANYWHERE);
			}
			if (MatchType.LIKEEND.equals(matchType)) {
				criterion = Restrictions.like(propertyNames[i], (String) propertyValues[i], MatchMode.END);
			}
			if (MatchType.LIKESTART.equals(matchType)) {
				criterion = Restrictions.like(propertyNames[i], (String) propertyValues[i], MatchMode.START);
			}
		}
		if(id != null){
			Criterion criterion = Restrictions.ne(getPKName(), id);
			c.add(criterion);
			list = c.list();
			return null == list || list.size() < 2;
		}else{
			list = c.list();
			return null == list || list.size() == 0;
		}
	}
	
	@SuppressWarnings("rawtypes")
	@Override
	public Integer findSqlInt(String querySql, List<String> propertyNames, List propertyValues) {
		Assert.hasText(querySql, "querySql不能为空");
		SQLQuery q = this.createSqlQuery(querySql, propertyNames, propertyValues);
		Object rs = q.uniqueResult();
		if (null != rs) {
			return new Integer(rs.toString());
		}
		return new Integer(0);
	}
	
	@SuppressWarnings("rawtypes")
	@Override
	public SQLQuery createSqlQuery(String querySql, List<String> propertyNames,
			List propertyValues) {
		Assert.hasText(querySql, "querySql不能为空");
		SQLQuery query = getSession().createSQLQuery(querySql);
		if (!CollectionUtils.isEmpty(propertyNames)) {
			for (int i = 0; i < propertyNames.size(); i++) {
				if (propertyValues.get(i) instanceof Collection || propertyValues.get(i) instanceof Object[]) {
					query.setParameterList(propertyNames.get(i), (Collection) propertyValues.get(i));
				} else {
					query.setParameter(propertyNames.get(i), propertyValues.get(i));
				}
			}
		}
		return query;
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<Map<String, Object>> setPageParameter(
			final Page<Map<String, Object>> page, final String querySql,
			List<String> propertyNames, List propertyValues) {
		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		SQLQuery query = this.createSqlQuery(querySql, propertyNames, propertyValues);
		if (page.getPageSize() != 0) {
			int start = page.getCurrentPage() * page.getPageSize();
			query.setFirstResult(start);
			query.setMaxResults(page.getPageSize());
			list = query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list();
		}
		return list;
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public List<T> setPageParameter(
			final Page<T> page, Class<T> clazz, final String querySql,
			List<String> propertyNames, List propertyValues) {
		List<T> list = new ArrayList<T>();
		SQLQuery query = this.createSqlQuery(querySql, propertyNames, propertyValues);
		if (page.getPageSize() != 0) {
			int start = page.getCurrentPage() * page.getPageSize();
			query.setFirstResult(start);
			query.setMaxResults(page.getPageSize());
			query.addEntity(clazz);
			list = query.list();
		}
		return list;
	}
	
	@SuppressWarnings("rawtypes")
	public SQLQuery setSQLQueryParameter(final Page<T> page, final String querySql, List<String> propertyNames, List propertyValues) {
		SQLQuery query = this.createSqlQuery(querySql, propertyNames, propertyValues);
		if (page.getPageSize() != 0) {
			int start = page.getCurrentPage() * page.getPageSize();
			query.setFirstResult(start);
			query.setMaxResults(page.getPageSize());
		}
		return query;
	}
}
